/// Minimum compatible version of OpenCASCADE library (major, minor)
///
/// Pre-installed OpenCASCADE library will be checked for compatibility using semver rules.
const OCCT_VERSION: (u8, u8) = (7, 8);

/// The list of used OpenCASCADE libraries which needs to be linked with.
const OCCT_LIBS: &[&str] = &[
    "TKMath",
    "TKernel",
    "TKFeat",
    "TKGeomBase",
    "TKG2d",
    "TKG3d",
    "TKTopAlgo",
    "TKGeomAlgo",
    "TKBRep",
    "TKPrim",
    "TKDESTEP",
    "TKDESTL",
    "TKMesh",
    "TKShHealing",
    "TKFillet",
    "TKBool",
    "TKBO",
    "TKOffset",
    "TKXSBase",
];

macro_rules! w_print {
    ($($tokens: tt)*) => {
        println!("cargo:warning={}", format!($($tokens)*))
    }
}

fn main() {
    let target = std::env::var("TARGET").expect("No TARGET environment variable defined");
    let is_windows = target.to_lowercase().contains("windows");
    let is_windows_gnu = target.to_lowercase().contains("windows-gnu");

    let occt_config = OcctConfig::detect(is_windows);

    println!("cargo:rustc-link-search=native={}", occt_config.library_dir.to_str().unwrap());

    let lib_type = if occt_config.is_dynamic { "dylib" } else { "static" };
    for lib in OCCT_LIBS {
        println!("cargo:rustc-link-lib={lib_type}={lib}");
    }

    if is_windows {
        println!("cargo:rustc-link-lib=dylib=user32");
    }

    let mut build = cxx_build::bridge("src/lib.rs");

    if is_windows_gnu {
        build.define("OCC_CONVERT_SIGNALS", "TRUE");
    }

    if let "windows" = std::env::consts::OS {
        let current = std::env::current_dir().unwrap();
        build.include(current.parent().unwrap());
    }

    if is_windows {
        let mut build = build
            .cpp(true)
            .flag_if_supported("-std=c++11")
            .define("_USE_MATH_DEFINES", "TRUE")
            .include(occt_config.include_dir)
            .include("include");

        // w_print!("Using Windows SDK paths:");
        // Add Windows SDK paths if available
        if let Some((sdk_version, sdk_paths)) = get_windows_sdk_paths() {
            println!("cargo:warning=Using Windows SDK version {}", sdk_version);
            for path in sdk_paths {
                println!("cargo:warning=Including path: {}", path);
                
                build = build.include(path);
            }
        }

        // Add MSVC path if available
        if let Some(msvc_root) = get_msvc_path() {
            if let Ok(entries) = std::fs::read_dir(&msvc_root) {
                if let Some(latest_version) = entries
                    .filter_map(|e| e.ok())
                    .filter(|e| e.path().is_dir())
                    .filter_map(|e| e.file_name().into_string().ok())
                    .max()
                {
                    let msvc_path = format!("{}\\{}\\include", msvc_root, latest_version);
                    println!("cargo:warning=Using MSVC path: {}", msvc_path);
                    build = build.include(msvc_path);
                }
            }
        }

        build.compile("wrapper");
    } else {
        //默认为 macos 的编译
        build
            .cpp(true)
            .flag_if_supported("-std=c++11")
            .define("_USE_MATH_DEFINES", "TRUE")
            .include(occt_config.include_dir)
            .include("include")
            .compile("wrapper");
    }

    println!("cargo:rustc-link-lib=static=wrapper");

    println!("cargo:rerun-if-changed=src/lib.rs");
    println!("cargo:rerun-if-changed=include/wrapper.hxx");
}

struct OcctConfig {
    include_dir: std::path::PathBuf,
    library_dir: std::path::PathBuf,
    is_dynamic: bool,
}

impl OcctConfig {
    fn detect(is_windows: bool) -> Self {
        let occt_root = std::env::var("OCC_ROOT").expect("OCC_ROOT environment variable not set");
        let occt_root = std::path::PathBuf::from(occt_root);

        if is_windows {
            return Self {
                include_dir: occt_root.join("include").join("opencascade"),
                library_dir: occt_root.join("lib"),
                is_dynamic: true,
            };
        } else {
            return Self {
                include_dir: occt_root.join("include").join("opencascade"),
                library_dir: occt_root.join("lib"),
                is_dynamic: true,
            };
        }
    }
    //
}

fn get_windows_sdk_paths() -> Option<(String, Vec<String>)> {
    use std::env;

    // Try to get Windows SDK version from environment
    let sdk_root = match env::var("WindowsSdkDir") {
        Ok(path) => {
            w_print!("Found WindowsSdkDir: {}", path);
            path
        },
        Err(e) => {
            w_print!("Failed to get WindowsSdkDir: {}", e);
            w_print!("Trying alternative methods...");

            // Try to find Windows SDK in common installation paths
            let common_paths = [
                "C:\\Program Files (x86)\\Windows Kits\\10",
                "C:\\Program Files\\Windows Kits\\10",
            ];

            for path in common_paths.iter() {
                if std::path::Path::new(path).exists() {
                    w_print!("Found Windows SDK in: {}", path);

                    // Try to find the latest version in the Include directory
                    let include_path = std::path::Path::new(path).join("Include");
                    if let Ok(entries) = std::fs::read_dir(&include_path) {
                        if let Some(latest_version) = entries
                            .filter_map(|e| e.ok())
                            .filter(|e| e.path().is_dir())
                            .filter_map(|e| e.file_name().into_string().ok())
                            .filter(|name| name.starts_with("10."))
                            .max()
                        {
                            w_print!("Found SDK version: {}", latest_version);
                            return Some((
                                latest_version.clone(),
                                vec![
                                    format!("{}\\Include\\{}\\ucrt", path, latest_version),
                                    format!("{}\\Include\\{}\\shared", path, latest_version),
                                    format!("{}\\Include\\{}\\um", path, latest_version),
                                ],
                            ));
                        }
                    }
                }
            }

            w_print!("Could not find Windows SDK in common paths");
            return None;
        },
    };

    let sdk_version = match env::var("WindowsSDKVersion") {
        Ok(version) => {
            w_print!("Found WindowsSDKVersion: {}", version);
            version.trim_end_matches('\\').to_string()
        },
        Err(e) => {
            w_print!("Failed to get WindowsSDKVersion: {}", e);

            // Try to find the latest version in the SDK root
            if let Ok(entries) = std::fs::read_dir(&sdk_root) {
                if let Some(latest_version) = entries
                    .filter_map(|e| e.ok())
                    .filter(|e| e.path().is_dir())
                    .filter_map(|e| e.file_name().into_string().ok())
                    .filter(|name| name.starts_with("10."))
                    .max()
                {
                    w_print!("Found SDK version in root: {}", latest_version);
                    latest_version
                } else {
                    w_print!("No valid SDK version found, using default 10.0.0.0");
                    String::from("10.0.0.0")
                }
            } else {
                w_print!("Failed to read SDK directory, using default version 10.0.0.0");
                String::from("10.0.0.0")
            }
        },
    };

    let include_paths = vec![
        format!("{}Include\\{}\\ucrt", sdk_root, sdk_version),
        format!("{}Include\\{}\\shared", sdk_root, sdk_version),
        format!("{}Include\\{}\\um", sdk_root, sdk_version),
    ];

    w_print!("Using Windows SDK paths:");
    for path in &include_paths {
        w_print!("  {}", path);
    }

    Some((sdk_version, include_paths))
}

fn get_msvc_path() -> Option<String> {
    use std::{env, process::Command};

    // Try to get MSVC path from environment first
    if let Ok(path) = env::var("VSINSTALLDIR") {
        return Some(format!("{}\\VC\\Tools\\MSVC", path));
    }

    // Otherwise try to use vswhere
    let output =
        Command::new("C:\\Program Files (x86)\\Microsoft Visual Studio\\Installer\\vswhere.exe")
            .args(["-latest", "-property", "installationPath"])
            .output()
            .ok()?;

    if output.status.success() {
        let vs_path = String::from_utf8_lossy(&output.stdout).trim().to_string();
        Some(format!("{}\\VC\\Tools\\MSVC", vs_path))
    } else {
        None
    }
}
